package kunsch.cli;

import java.io.File;
import java.util.ArrayList;
import java.util.List;

import kunsch.bdd.AbstractBDD;
import kunsch.bdd.BBDCreator;
import kunsch.bdd.BDDReduceStrategy;
import kunsch.bdd.QROBDDReduceStrategy;
import kunsch.bdd.ROBDDReduceStrategy;
import kunsch.bdd.util.DOTCreator;
import kunsch.bexp.BooleanFunction;
import kunsch.bexp.Expression;
import kunsch.bexp.Variable;
import kunsch.bexp.transform.ShannonExpander;
import kunsch.bexp.transform.UserDefinedVariableOrderStrategy;
import kunsch.bexp.transform.WeightHeuristicVariableOrderStrategy;
import kunsch.read.BLIFReader;

import org.apache.commons.cli.BasicParser;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.OptionBuilder;
import org.apache.commons.cli.OptionGroup;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.ParseException;

public abstract class Cli {

	public static String DOT_EXE_PATH = null;
	private static Options options;

	public static void main(String[] args) {

		Cli.options = new Options();

		Cli.options.addOption("h", "help", false, "Display this help.");

		OptionBuilder.withLongOpt("dot");
		OptionBuilder.isRequired();
		OptionBuilder.hasArg();
		OptionBuilder.withArgName("dot-executable");
		OptionBuilder.withDescription("Create *.png-graphs with Graphviz/dot.");
		Cli.options.addOption(OptionBuilder.create('d'));

		OptionBuilder.withLongOpt("files");
		OptionBuilder.isRequired();
		OptionBuilder.hasArg();
		OptionBuilder.withArgName("blif-files");
		OptionBuilder
				.withDescription("Specify blif-files (separated with commas) to process.");
		Cli.options.addOption(OptionBuilder.create('f'));

		OptionGroup variableOrder = new OptionGroup();

		OptionBuilder.withLongOpt("customOrder");
		OptionBuilder.hasArg();
		OptionBuilder.withArgName("variables");
		OptionBuilder
				.withDescription("Specify custom variable order (separated with commas).");

		variableOrder.addOption(OptionBuilder.create('c'));

		OptionBuilder.withLongOpt("semiCustomOrder");
		OptionBuilder.hasArg();
		OptionBuilder.withArgName("variables");
		OptionBuilder
				.withDescription("Specify custom variable order (separated with commas) and wildcard ('*'), which is replaced with missing variables determined by weight-heuristics.");

		variableOrder.addOption(OptionBuilder.create('s'));

		variableOrder.addOption(new Option("w", "weightOrder", false,
				"Order variables with weight-heuristics."));

		Cli.options.addOptionGroup(variableOrder);

		OptionGroup reduceStrategy = new OptionGroup();

		OptionBuilder.withLongOpt("reduce");
		OptionBuilder.withDescription("Reduce resulting BDD (ROBDD).");

		reduceStrategy.addOption(OptionBuilder.create('r'));

		OptionBuilder.withLongOpt("qreduce");
		OptionBuilder.withDescription("Quasireduce resulting BDD (QROBDD).");

		reduceStrategy.addOption(OptionBuilder.create('q'));

		Cli.options.addOptionGroup(reduceStrategy);

		CommandLineParser commandLineParser = new BasicParser();

		try {

			CommandLine commandLine = commandLineParser.parse(options, args);

			if (commandLine.getArgs().length > 0) {
				System.out.println("Error: Unkown arguments given.");
				System.out.println();
				Cli.displayHelp();
				return;
			}

			if (commandLine.hasOption('h')) {
				Cli.displayHelp();
				return;
			}

			String[] pathnames = commandLine.getOptionValue('f').split(",");
			List<File> filesToProcess = new ArrayList<>();
			for (String pathname : pathnames) {
				File fileToProcess = new File(pathname);
				if (!fileToProcess.exists()) {
					System.out.println("Error: File \"" + pathname
							+ "\" does not exist.");
					return;
				}
				if (!fileToProcess.canRead()) {
					System.out.println("Error: File \"" + pathname
							+ "\" cannot be read.");
					return;
				}
				if (fileToProcess.isDirectory()) {
					System.out.println("Error: File \"" + pathname
							+ "\" is a directory.");
					return;
				}
				filesToProcess.add(fileToProcess);
			}

			for (File fileToProcess : filesToProcess) {

				BLIFReader blifReader = new BLIFReader();
				BooleanFunction function = blifReader.readFile(fileToProcess);

				List<Variable> orderedVariables = null;

				if (commandLine.hasOption('w')) {

					orderedVariables = new WeightHeuristicVariableOrderStrategy(
							function).order(function.getVariables());

				}

				if (commandLine.hasOption('s')) {

					List<Variable> tmpOrderedVariables = new WeightHeuristicVariableOrderStrategy(
							function).order(function.getVariables());

					List<String> weightVaribles = new ArrayList<>();

					for (Variable tmpOrderedVariable : tmpOrderedVariables) {
						weightVaribles.add(tmpOrderedVariable.getDescription());
					}

					String variables = commandLine.getOptionValue('s');
					String[] customVariables = variables.split(",");

					for (String customVariable : customVariables) {
						weightVaribles.remove(customVariable);
					}

					List<String> newVariables = new ArrayList<>();

					for (String customVariable : customVariables) {

						if (customVariable.equals("*")) {
							newVariables.addAll(weightVaribles);
						} else {
							newVariables.add(customVariable);
						}
					}
					
					
					orderedVariables = new UserDefinedVariableOrderStrategy(
							(String[]) newVariables.toArray(new String[0]))
							.order(function.getVariables());

				}

				if (commandLine.hasOption('c')) {

					String variables = commandLine.getOptionValue('c');

					orderedVariables = new UserDefinedVariableOrderStrategy(
							variables.split(","))
							.order(function.getVariables());

				}

				if (orderedVariables == null) {
					orderedVariables = function.getVariables();
				}

				Expression expandedExpression = ShannonExpander.expand(
						orderedVariables, function.getIn());

				BBDCreator creator = new BBDCreator();

				BDDReduceStrategy bddReduceStrategy = null;

				if (commandLine.hasOption('r')) {
					bddReduceStrategy = new ROBDDReduceStrategy(
							orderedVariables.size());
				}

				if (commandLine.hasOption('q')) {
					bddReduceStrategy = new QROBDDReduceStrategy(
							orderedVariables.size());
				}

				AbstractBDD bdd = creator.transformToBDD(expandedExpression,
						orderedVariables.size(), bddReduceStrategy);

				// create .dot file which can be translated to a png image
				// with graphviz
				DOTCreator dot = new DOTCreator();
				File dotFile = dot.createDotFile(bdd,
						fileToProcess.getAbsolutePath() + ".dot");
				dot.createDotPngFile(commandLine.getOptionValue('d'), dotFile,
						fileToProcess.getAbsolutePath() + ".png");

			}

		} catch (ParseException e) {

			System.out.println("Error: " + e.getMessage());
			System.out.println();
			Cli.displayHelp();

		}

	}

	private static void displayHelp() {

		HelpFormatter helpFormatter = new HelpFormatter();
		helpFormatter.printHelp("bddlib", Cli.options, true);

	}

}
